use chrono::Local;
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::path::Path;
use std::sync::{Mutex};
use std::{fs, io};

lazy_static! {
    pub static ref WD_LOG_CONFIG: LogConfig = LogConfig::new();
}

#[derive(Eq, PartialEq, Hash, Copy, Clone)]
pub enum Level {
    PANIC,
    ERROR,
    WARN,
    INFO,
    DEBUG,
}
impl Level {
    pub fn as_u8(&self) -> u8 {
        match *self {
            PANIC => 1,
            ERROR => 2,
            WARN => 3,
            INFO => 4,
            DEBUG => 5,
        }
    }
    pub fn to_string(&self) -> String {
        match self {
            Level::PANIC => String::from("PANIC"),
            Level::ERROR => String::from("ERROR"),
            Level::WARN => String::from("WARN"),
            Level::INFO => String::from("INFO"),
            Level::DEBUG => String::from("DEBUG"),
        }
    }
}

impl<T> From<T> for Level
where
    T: ToString,
{
    fn from(level: T) -> Self {
        let level = level.to_string();
        // let level = String::from_utf8_lossy(level.as_bytes()).to_string();
        // let level = String::from_utf8_lossy(level.as_bytes()).to_string();
        match level.to_lowercase().as_str() {
            "panic" => PANIC,
            "1" => PANIC,
            "error" => ERROR,
            "2" => ERROR,
            "warn" => WARN,
            "3" => WARN,
            "info" => INFO,
            "4" => INFO,
            "debug" => DEBUG,
            "5" => DEBUG,
            _ => INFO,
        }
    }
}
// pub type Level = u8;
pub const PANIC: Level = Level::PANIC;
pub const ERROR: Level = Level::ERROR;
pub const WARN: Level = Level::WARN;
pub const INFO: Level = Level::INFO;
pub const DEBUG: Level = Level::DEBUG;

pub struct LogConfig {
    pub is_std_out: bool,
    pub is_file_out: bool,
    pub out: Mutex<Box<dyn io::Write + Send + Sync + 'static>>,
    pub level: Level,
    pub level_map: HashMap<Level, String>,
    pub prefix: &'static str, //前缀
    pub print_time: bool,     //是否打印时间
    pub file_and_line: bool,  //是否打印行号
}

impl LogConfig {
    pub fn new() -> LogConfig {
        let mut lp = HashMap::new();
        lp.insert(DEBUG, "DEBUG".to_string());
        lp.insert(INFO, "INFO ".to_string());
        lp.insert(WARN, "WARN ".to_string());
        lp.insert(ERROR, "ERROR".to_string());
        lp.insert(PANIC, "PANIC".to_string());
        LogConfig {
            is_std_out: true,
            out: Mutex::new(Box::new(io::stdout())),
            level: DEBUG,
            level_map: lp,
            prefix: "wd_log",
            print_time: true,
            file_and_line: true,
            is_file_out: false,
        }
    }
    pub fn set_level(&mut self, level: Level) {
        self.level = level;
    }
    pub fn close_output(&mut self){
        self.is_std_out = false
    }
    pub fn set_prefix(&mut self, prefix: &'static str) {
        self.prefix = prefix;
    }
    pub fn show_time(&mut self, ok: bool) {
        self.print_time = ok;
    }
    pub fn show_file_line(&mut self, ok: bool) {
        self.file_and_line = ok;
    }
    pub fn output_to_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
        let file = fs::OpenOptions::new()
            .create(true)
            .write(true)
            .append(true)
            .open(path)?;
        self.out = Mutex::new(Box::new(file));
        self.is_file_out = true;
        Ok(())
    }
}

impl Drop for LogConfig {
    fn drop(&mut self) {
        if !self.is_std_out {
            let mut file = self.out.lock().unwrap();
            let _ = file.flush();
        }
    }
}

pub fn set_level(level: Level) {
    #[allow(invalid_reference_casting)]
    unsafe {
        let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
        log.set_level(level)
    }
}
pub fn close_output(){
    #[allow(invalid_reference_casting)]
    unsafe {
        let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
        log.close_output();
    }
}
pub fn set_prefix(prefix: &'static str) {
    #[allow(invalid_reference_casting)]
    unsafe {
        let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
        log.set_prefix(prefix);
    }
}
pub fn show_time(show: bool) {
    #[allow(invalid_reference_casting)]
    unsafe {
        let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
        log.show_time(show)
    }
}
pub fn show_file_line(show: bool) {
    #[allow(invalid_reference_casting)]
    unsafe {
        let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
        log.show_file_line(show)
    }
}
pub fn output_to_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
    #[allow(invalid_reference_casting)]
    unsafe {
        let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
        log.is_std_out = false;
        log.output_to_file(path)
    }
}

pub fn get_level() -> Level {
    WD_LOG_CONFIG.level
}
pub fn get_prefix() -> &'static str {
    #[allow(invalid_reference_casting)]
    unsafe {
        let log = &mut *(&*WD_LOG_CONFIG as *const LogConfig as *mut LogConfig);
        log.prefix
    }
}

pub fn output(level: Level, file: &str, line: u32, mut fmt: String) -> io::Result<()> {
    let log = &WD_LOG_CONFIG;
    if level.as_u8() > log.level.as_u8() {
        return Ok(());
    }
    let t = if log.print_time {
        Local::now().format("%Y-%m-%d %H:%M:%S%.3f ").to_string()
    } else {
        String::new()
    };
    let level_name = log.level_map.get(&level).unwrap();
    let file_line = if file.len() > 0 && log.file_and_line {
        format!("{}({}):", file, line)
    } else {
        String::new()
    };
    if log.is_std_out {
        let mut fmt_last = "".to_string();
        if let Some(s) = fmt.as_bytes().last() {
            if *s == '\n' as u8 {
                fmt_last.push(fmt.pop().unwrap())
            }
        }
        let buf = match level.as_u8() {
            1 => {
                format!(
                    "\x1b[7;31m[{}{} {}]{} {}\x1b[0m{}",
                    t, level_name, log.prefix, file_line, fmt, fmt_last
                )
            }
            2 => {
                format!(
                    "\x1b[7;31m[{}{} {}]{} {}\x1b[0m{}",
                    t, level_name, log.prefix, file_line, fmt, fmt_last
                )
            }
            3 => {
                format!(
                    "\x1b[33m[{}{} {}]{} {}\x1b[0m{}",
                    t, level_name, log.prefix, file_line, fmt, fmt_last
                )
            }
            4 => {
                format!(
                    "\x1b[32m[{}{} {}]{} {}\x1b[0m{}",
                    t, level_name, log.prefix, file_line, fmt, fmt_last
                )
            }
            5 => {
                format!(
                    "\x1b[32m[{}{} {}]{} {}\x1b[0m{}",
                    t, level_name, log.prefix, file_line, fmt, fmt_last
                )
            }
            _ => String::new(),
        };
        print!("{}", buf);
        // return Ok(());
    }
    if !log.is_file_out {
        return Ok(())
    }
    let buf = match level.as_u8() {
        1 => {
            format!("[{}{} {}]{} {}", t, level_name, log.prefix, file_line, fmt)
        }
        2 => {
            format!("[{}{} {}]{} {}", t, level_name, log.prefix, file_line, fmt)
        }
        3 => {
            format!("[{}{} {}]{} {}", t, level_name, log.prefix, file_line, fmt)
        }
        4 => {
            format!("[{}{} {}]{} {}", t, level_name, log.prefix, file_line, fmt)
        }
        5 => {
            format!("[{}{} {}]{} {}", t, level_name, log.prefix, file_line, fmt)
        }
        _ => String::new(),
    };
    let mut out = log.out.lock().unwrap();
    out.write_all(buf.as_bytes())
}

#[cfg(test)]
mod test{
    use lazy_static::lazy_static;
    use crate::log_config::LogConfig;

    lazy_static!{
        pub static ref CONFIG:LogConfig = LogConfig::new();
    }


    #[test]
    fn test(){
        #[allow(invalid_reference_casting)]
        unsafe {
            let ptr = &mut *(&*CONFIG as *const LogConfig as *mut LogConfig);
            ptr.prefix = "hello";
            println!("config:{}",CONFIG.prefix)
        }
    }
}
